
#include "PDFWriteProcess.hpp"
#include "datakit.h"
#include <iostream>
#include "tess/tess.h"
#include "common/logger.h"
#include "./utils/FileUtil.h"
#include "./utils/StringUtil.h"
#include "pdf/pdfw.hpp"
#include "PDFWriter.hpp"
#ifndef CXX_LESS_17
#include <filesystem>
#else
    #include <codecvt>
    #include <locale>
#endif

namespace AMCAX::Exchange
{
    PDFWriteProcess::PDFWriteProcess(Dtk_string inputFile, std::unique_ptr<WriterBase> writer)
        : WriteProcessBase(inputFile, std::move(writer))
    {

    }

    Dtk_ErrorStatus PDFWriteProcess::WriteDocument(Dtk_MainDocPtr inDocument, const std::string& outputFile)
    {
        if (inDocument.IsNULL())
            return dtkErrorNullPointer;

        // First we get the root component in the document
        Dtk_ComponentPtr rootComponent = inDocument->RootComponent();
        if (rootComponent.IsNULL())
        {
            return dtkErrorNullPointer;
        }

        // Fix the file type if needed.
        std::string outputFilePath(outputFile);
        auto extension = StringUtil::ToLowerCase(FileUtil::GetExtension(outputFile));
        auto isAssembly = rootComponent->IsAssembly();

        // if no Error we write the Component
        if (m_fileWriter)
        {
            auto err = m_fileWriter->WriteInit(m_inputFile, outputFilePath.c_str());
            if (err != dtkNoError)
            {
                LOG_ERROR("Write Init with error {}", (int)err);
                return err;
            }
        }

        PdfInitComponent(rootComponent);
        // Go Through the Root Component and children
        const Dtk_transfo &inMatrix = Dtk_transfo();
        WriteComponent(rootComponent, inMatrix, false);    

        PdfEndComponent();

        // Build assembly part relationships.
        // BuildPartAssemblyRelationships(outputFilePath);

        if (m_fileWriter)
        {
            auto err = m_fileWriter->WriteEnd();
            if (err != dtkNoError)
            {
                LOG_ERROR("Write End with error {}", (int)err);
                return err;
            }
        }
        return dtkNoError;
    }

    Dtk_ErrorStatus PDFWriteProcess::WriteInstance(Dtk_ComponentPtr inComponent)
    {

        // Instance represent a prototype with a matrix placement
        Dtk_string componentName;
        componentName = inComponent->Name(); // Component name
        Dtk_ComponentPtr prototype = inComponent->GetChild(0);
        Dtk_transfo matrix = inComponent->TransformationMatrix();
        Dtk_ID childID = inComponent->GetChild(0)->GetID();


        Dtk_ID pdfInstID = PdfInitInstance(inComponent);
        
        // you have to write matrix and instance the prototype
        WriteComponent(prototype, Dtk_transfo(), ComponentProcessed.find(childID) < 0);

        
        PdfEndInstance(pdfInstID, childID);

        if (ComponentProcessed.find(childID) < 0)
        {
            ComponentProcessed.push_back(childID);
        }

        return dtkNoError;
    }

    Dtk_ErrorStatus PDFWriteProcess::WriteComponent(Dtk_ComponentPtr inComponent, const Dtk_transfo& inMatrix, bool bCreateFile)
    {
        Dtk_Size_t i;

        // Datakit component are given in MM, if original model had other unit you can get it with GetConceptionUnitScale()
        //  unitFactor = 25.4 for inch
        double unitFactor;
        Dtk_ErrorStatus err;
        err = inComponent->GetConceptionUnitScale(unitFactor);

        // GetAttributes
        Dtk_InfoPtr attributes = inComponent->GetInfos();
        if (attributes.IsNotNULL())
        {
            int compoActivationStatus, compoBlankedStatus;
            Dtk_RGB compoColor;
            compoActivationStatus = attributes->GetActivationFlag(); // if compoActivationStatus == 0 Component and his children aren't visible
            compoBlankedStatus = attributes->GetBlankedStatus();     // if compoBlankedStatus == 1 Component and his children aren't visible
            compoColor = attributes->GetColor();                     // if compoBlankedStatus == 1 Component and his children are using this color
        }

        if (m_fileWriter && bCreateFile)
        {
            m_fileWriter->InitComponent(inComponent);
        }
        // You have 4 types for Component
        Dtk_Component::ComponentTypeEnum type = inComponent->ComponentType();

        switch (type)
        {
            // Instance represent a prototype with a matrix placement
        case Dtk_Component::InstanceComponentType:
        {
            WriteInstance(inComponent);
            break;
        }
        // Prototype (you have to check if you ever read and write it to don't waste time)
        // you can use the method inComponent->GetID() to get Unique ID for Component
        case Dtk_Component::PrototypeComponentType:
        {
            Dtk_ID outId;
            WritePrototype(inComponent, inMatrix, outId);
            break;
        }
        // Catalog Component represent a choice of several possible configuration
        //(like scene in catiav5, workspace in catiav4, configuration in solidworks)
        // Default is the first child
        case Dtk_Component::CatalogComponentType:
        {
            Dtk_string name = inComponent->Name(); // Component name

            Dtk_Size_t numComp = inComponent->GetNumChildren();
            if (numComp > 0)
            {
                Dtk_Int32 defaultindex = inComponent->GetDefaultChildInCatalog(); // Get Default child to use
                Dtk_ComponentPtr defaultchoice = inComponent->GetChild(defaultindex);
                if (defaultchoice.IsNotNULL())
                {
                    WriteComponent(defaultchoice, inMatrix, true);
                }
            }
            // if you don't want to use default you have to scan all children and choose the one you want to convert (see their name)
            break;
        }
        // Component containing only children
        case Dtk_Component::VirtualComponentType:
        {
            Dtk_string name;
            Dtk_Size_t NumChildren;

            name = inComponent->Name(); // Component name

            NumChildren = inComponent->GetNumChildren();
            for (i = 0; i < NumChildren; i++)
            {
                Dtk_ComponentPtr child = inComponent->GetChild(i);
                WriteComponent(child, inMatrix);
            }
            break;
        }
        }

        if (m_fileWriter && bCreateFile)
        {
            auto name = inComponent->Name(); // Component name
            if(inComponent->IsAssembly())
            {
                m_asseblyIdAndNameMap[inComponent->GetID()] = inComponent->Name();
            }            
            m_fileWriter->EndComponent(inComponent);
        }
        return dtkNoError;
    }

    Dtk_ErrorStatus PDFWriteProcess::WritePrototype(Dtk_ComponentPtr inComponent, const Dtk_transfo& inMatrix, Dtk_ID &outPrototypeId)
    {
        Dtk_ErrorStatus err = dtkNoError;
        Dtk_Size_t i;
        Dtk_ID componentID = inComponent->GetID();
        Dtk_Int32 componentIndex;
        componentIndex = ComponentProcessed.find(componentID);

        if (componentIndex == -1)
        {
            Dtk_API *inAPI = Dtk_API::GetAPI();
            // it can also contain some instances
            Dtk_Size_t numChildren = inComponent->GetNumChildren();
            if(numChildren > 0)
            {
                m_asseblyIdAndNameMap[componentID] = inComponent->Name();
            }
            for (i = 0; i < numChildren; i++)
            {
                Dtk_ComponentPtr child = inComponent->GetChild(i);
                Dtk_ID childComponentID = child->GetID();
                m_assemblyPartsInfoMap[componentID].push_back(InstanceInfo(child->Name(), child->TransformationMatrix(), child->IsAssembly(), componentID));
                WriteComponent(child, inMatrix, false);
            }
            Dtk_NodePtr rootNode;

            // Get the Construction tree for this prototype
            if (inComponent->ComponentAvailability() == Dtk_Component::ComponentMissing)
                err = dtkErrorFileNotExist;
            else
                err = inAPI->ReadComponent(inComponent, rootNode);

            std::cout << "Read Component " << inComponent->Name().c_str() << std::endl;

            // a rootNode=  NULL with err == dtkNoError means that component is empty
            if (err == dtkNoError && rootNode.IsNotNULL())
            {
                // Set matrix to apply to all node if you flat assemblies
                m_currentMatrix = inMatrix;

                Dtk_tab<Dtk_ID> outIds;
                // Go Through the Root Node and children
                WriteNode(rootNode, outIds);

                if (m_fileWriter)
                {
                    m_fileWriter->WriteGlobalDataSet(inComponent->GetGlobalDataSet());
                    auto isAssembly = inComponent->IsAssembly();
                    Dtk_Size_t numMetaData;
                    numMetaData = inComponent->GetNumMetaData();
                    if (numMetaData)
                    {
                        for (i = 0; i < numMetaData; i++)
                        {
                            // For assembly component, we need to store the metadata in the map for later adding because wedidn't
                            // open the assembly yet.
                            // For part component, we can write it directly.
                            if(isAssembly)
                            {
                                m_assemblyMetaDataMap[componentID].push_back(inComponent->GetMetaData(i));
                            }
                            else
                            {
                                m_fileWriter->WriteMetaData(inComponent->GetMetaData(i));
                            }
                        }
                    }
                }
            }

            PdfWriteMetaData(inComponent);

            // We close the opened Component and free his construction tree
            inAPI->EndComponent(inComponent);
        }
        else
        {

            PdfInstanceExistingPrototype(componentIndex);
        }
        return err;
    }

    void PDFWriteProcess::WriteBody(Dtk_NodePtr inNode, Dtk_tab<Dtk_ID>& outIds)
    {
                // Get if the node represent infinite geometry
        // 1 = Infinite, 0 = Finite
        int NodeInfiniteGeometry = inNode->GetInfos()->GetInfiniteGeometryFlag();
        if (NodeInfiniteGeometry == 0)
        {
            //Calling both methods GetDtk_MeshPtr() and GetDtk_BodyPtr() on BodyNode will give you the same result
            //Choose the one you need in order to avoid duplicated data
            Dtk_BodyPtr TmpBody = inNode->GetDtk_BodyPtr();
            if (TmpBody.IsNotNULL())
            {
                if (m_fileWriter)
                {
                    m_fileWriter->WriteBody(TmpBody, outIds);
                }
            }

            // Some CAD formats store also faceted data besides B-Rep. 
            // So you can get faceted data corresponding to the body using the following method
            const Dtk_MeshPtr TmpFacetedBody = inNode->GetDtk_MeshPtr();
            if (TmpFacetedBody.IsNotNULL())
            {
                TmpFacetedBody->Transform(m_currentMatrix);
                WriteDtk_Mesh(TmpFacetedBody, outIds);
            }

            // If there is no mesh data associated to the current body, you can tessellate (and if init of tessellation engine is successful)
            if (TmpFacetedBody.IsNULL() && TmpBody.IsNotNULL() && GetTesselationStatus() == dtkNoError)
            {
                Dtk_tab<Dtk_MeshPtr> meshes;
                Dtk_tab<Dtk_Int32> isclosed;
                int err_tess = tess_BodyToMeshes(TmpBody, meshes, isclosed);
                if( inNode->Name().len() > 0 )
                    std::cout << "NodeName = " << inNode->Name().c_str() << ";";
                std::cout << "err_tess = " << dtkTypeError( err_tess ).c_str() << std::endl ;
                if (err_tess == 0)
                {
                    Dtk_Size_t i, nbmeshes = meshes.size();
                    for (i = 0; i < nbmeshes; i++)
                    {
                        meshes[i]->Transform(m_currentMatrix);
                        WriteDtk_Mesh(meshes[i], outIds);
                    }
                }

                
                if (TmpBody->HasWire())
                {// handling wire / points
                    PdfWriteEntity( Dtk_EntityPtr::DtkDynamicCast( TmpBody ));
                }
            }
        }
    }

        
    Dtk_ID PDFWriteProcess::PdfInitInstance(Dtk_ComponentPtr inComponent)
    {
        Dtk_ID pdfInstID;
        Dtk_string ComponentName;
        ComponentName = inComponent->Name();//Component name

        Dtk_InfoPtr infos = inComponent->GetInfos();
        Dtk_transfo instanceTransfo;

        if (inComponent->InstanceName().len() > 0)
            ComponentName += " (" + inComponent->InstanceName() + ")";

        instanceTransfo = inComponent->TransformationMatrix();
        Pdfw::InitNode(ComponentName, instanceTransfo, infos, pdfInstID);
        Pdfw::StorePrototype(pdfInstID);
        return pdfInstID;
    }

    void PDFWriteProcess::PdfEndInstance(Dtk_ID pdfInstID, Dtk_ID childID)
    {
        if ( ComponentProcessed.find(childID) < 0)
        {
            ComponentProcessed.push_back(pdfInstID);
        }
        Pdfw::EndNode();
    }

    void PDFWriteProcess::PdfWriteMetaData(Dtk_ComponentPtr inComponent)
    {
        Dtk_Size_t i,NumMetaData;
        NumMetaData = inComponent->GetNumMetaData();
        if (NumMetaData)
        {
            Dtk_tab<Dtk_MetaDataPtr> tabMetadata;
            for (i = 0; i < NumMetaData; i++)
            {
                tabMetadata.push_back(inComponent->GetMetaData(i));
            }
            Pdfw::AddNodeMetadata(tabMetadata);
        }
    } 

    void PDFWriteProcess::PdfWriteMetaData(Dtk_tab<Dtk_MetaDataPtr> &inTabMetadata)
    {
        Pdfw::AddNodeMetadata(inTabMetadata);	
    }


    void PDFWriteProcess::PdfInstanceExistingPrototype(Dtk_ID ComponentIndex)
    {
        Pdfw::ReinstanciatePrototype(ComponentProcessed[ComponentIndex]);
    }

    Dtk_ID PDFWriteProcess::PdfInitComponent(Dtk_ComponentPtr inComponent)
    {
        Dtk_InfoPtr infos = inComponent->GetInfos();
        Dtk_string name = inComponent->Name();
        Dtk_ID pdfID;
        Pdfw::InitNode(name, Dtk_transfo(), infos, pdfID);
        return pdfID;
    }

    void PDFWriteProcess::PdfEndComponent()
    {
        Pdfw::EndNode();
    }

    Dtk_ID PDFWriteProcess::PdfInitNode(Dtk_NodePtr inNode)
    {
        Dtk_ID pdfID;
        Dtk_string NodeName = inNode->Name();
        Dtk_InfoPtr nodeInfo = inNode->GetInfos();
        Dtk_bool nodeVisi = nodeInfo->GetBlankedStatus() ? DTK_FALSE : DTK_TRUE;
        Pdfw::InitNode(NodeName, Dtk_transfo(), nodeVisi, pdfID);
        if (nodeInfo.IsNotNULL())
            Pdfw::AddNodeRenderInfo(nodeInfo->GetRenderInfos());
        return pdfID;
    }

    void PDFWriteProcess::PdfEndNode()
    {
        Pdfw::EndNode();
    }

    void PDFWriteProcess::PdfWriteEntity(Dtk_EntityPtr inEntity)
    {
        PDFWriter* pdfWriter = dynamic_cast<PDFWriter*>(m_fileWriter.get());
        if (pdfWriter) {
            pdfWriter->WriteEntity(inEntity);
        } 
    }

    // void PDFWriteProcess::PdfWriteDrawingNode(Dtk_NodePtr inNode)
    // {
    //     Dtk_MetaDataPtr meta = Dtk_MetaData::CreateMetaData(Dtk_MetaData::TypeFileProperty, "See Other Page For Drawing", inNode->Name());
    //     Dtk_tab< Dtk_MetaDataPtr> tabmeta;
    //     tabmeta.push_back(meta);
    //     PdfWriteMetaData(tabmeta);
    //     Dtk_DrawingPtr TmpDrawing = inNode->GetDtk_DrawingPtr();
    //     Pdfw::WriteEntity(Dtk_EntityPtr::DtkDynamicCast(TmpDrawing));
    //     modelInventory[Dtk_Node::DrawingType]++;
    // }

}
